""" 

    Timer and thread helpers.
    
"""

__author__ = "Mikko Ohtamaa <mikko@redinnovation.com>"
__docformat__ = "epytext"
__copyright__ = "Red Innovation Ltd."
__license__ = "BSD"


# Python modules
import time

# S60 modules
import e32
import thread

import movz.client.generic.thread as genericthread

# timer that overcomes regular timer interval limitation
# overcomes two bugs:
# - http://sourceforge.net/tracker/index.php?func=detail&aid=1481781&group_id=154155&atid=790646
# - ao_timer does not properly reference count the callback
# parameter in after()?

# please note that if application is shutting down, active scheduler must
# be called so that callbacks are properly called

_garbage = [] # Ao_timer reference count bug

class ExtAoTimer:
    def __init__(self, logger = None):
        self._timer = e32.Ao_timer()
        self.canceled = False

    def after(self, delay, callback):
        global _garbage
        
        if self.canceled:
            del _garbage[_garbage.index(self)]
            return
        else:
            if self not in _garbage:
                _garbage.append(self)

        MAX = 2147
        if delay > MAX:
            self._timer.after(MAX, lambda : self.after(delay-MAX, callback))
        else:
            def check_and_call():
                if self.canceled:
                    del _garbage[_garbage.index(self)]
                    return
                else:
                    try:
                        callback()
                    finally:
                        del _garbage[_garbage.index(self)]
            self._timer.after(delay, check_and_call)
        
    def cancel(self):
        self.canceled = True
        self._timer.cancel()

class TimeoutLock(genericthread.TimeoutLock):
    """ Lock object with timeout.
    
    Workaround pesky S60 issues.
    
    Timeout prevents the ternal waits in the case of 
    error behaviour.
    """
        
    def __init__(self, name):
        genericthread.TimeoutLock.__init__(self, name)
        self.reset()
        
    def reset(self):        
        genericthread.TimeoutLock.reset(self)
        self.timer = None
        self.lock = None
                
    def signal(self, reason = "signal() called"):
        
        if self.signal_reason:
            # Already triggered
            return
        
        self.signal_reason = reason                
        self.signaled = True
        
        if self.lock != None:
            self.lock.signal()
            self.lock = None
        
        #if self.timer != None:
            #
            # NOTE TO SELF: NEVER EVER CALL Ao_timer.cancel()
            # It crashes your app, your symbian, your life.
            # If you are going to use timer.cancel() prepare
            # a noose for yourself.
            #self.timer.cancel()
            
            # Let the timer die by ticking out of the juice
        #    self.timer = None
        
                        
    def trigger_timeout(self):
        #self.timer = None
        self.signal("timeout triggered")
        
        
    def wait(self, timeout):        
        """ Wait until lock is signalled or specific timeout
        
        @param timeout: Timeout in seconds.
        """
        
        if self.signal_reason:
            return 
        
        if self.timer == None:
            self.timer = ExtAoTimer() 
            self.timer.after(timeout, self.trigger_timeout)
            pass
            
        if self.lock == None and not self.signal_reason:
            # Something signalled this lock before we reached wait
            self.lock = e32.Ao_lock()
            self.lock.wait()
        
        if self.timer != None:
            # Clean-up the timer, or we will pile up
            # waitout timers and run out of resources
            
            try:
                self.timer.cancel()
            except AttributeError:
                # Race condition self.time = None case
                # TODO: This si a hack and we need proper 
                # sync primitives around timer and lock                
                pass
            self.timer = None
            
class Signaller:
    """ Wraps one native PyS60 Lock object to a callable method.
    
    Example::
    
        lock = SafeLock()
        s = Signaller(lock)
        
        s() # Release the lock, the caller is not aware about the lock implementation
    
    Notify main thread that there is logging coming through and it should abort idling. """
    def __init__(self, lock, reason = "Signaller instance triggered"):
        """
        
        @param lock: SafeLock object
        """
        self.lock = lock
        self.reason = reason

         
    def __call__(self):
        """ Trigger the lock. """
        try:        
            self.lock.signal(self.reason)
        except AttributeError:
            # self.lock = None
            # This is some kind of racing condition in threading
            # code which should not be possible to happen, but happens anyway...
            pass 
        
